package readWriteFieldsRelations;


import org.objectweb.asm.ClassReader;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.FieldInsnNode;
import org.objectweb.asm.tree.InsnList;
import org.objectweb.asm.tree.MethodInsnNode;
import org.objectweb.asm.tree.MethodNode;

import util.CanBeUsedAs;
import util.Log;

import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;
import java.util.*;

import model.Filter;
import model.StatementKind;
import model.MethodDeclared;
import model.ConstructorDeclared;
public class MethodRelations_ implements Opcodes {
	  public static final String lineSep = System.getProperty("line.separator");

	//all classes
	  private final Collection<Class<?>> cluster;
	  private final Collection<Class<?>> classesToTest;
	  
	  //collections to keep method dependence
	  protected  Map<Class<?>, Map<Method, ReadWriteFields>> fieldReadWrites =
	    new LinkedHashMap<Class<?>, Map<Method, ReadWriteFields>>();  
	  protected  Map<Class<?>, ReadWriteFields> constructorReadWrites =
		    new LinkedHashMap <Class<?>, ReadWriteFields>();  

	  protected final Map<Class<?>, Map<StatementKind, List<StatementKind>>> dependences =
	    new LinkedHashMap<Class<?>, Map<StatementKind, List<StatementKind>>>();  
	  protected final Map<Class<?>, List<StatementKind>> constructorDependences =
	    new LinkedHashMap<Class<?>, List<StatementKind>>();
	  protected final Map<Class<?>, Map<String, List<StatementKind>>> statementWriteFiled =
		    new LinkedHashMap<Class<?>, Map<String, List<StatementKind>>>();
	  protected final Map<Class<?>, Map<String, List<StatementKind>>> privateDependences =
		    new LinkedHashMap<Class<?>, Map<String, List<StatementKind>>>();
 
	  /**
	   * Package visible constructor
	   * */
	  MethodRelations_(Collection<Class<?>> classesToTest,Collection<Class<?>> cluster) {
		  this.classesToTest=classesToTest;
	    this.cluster = cluster;
	  }
	  public Map<Class<?>, Map<String, List<StatementKind>>> getStatementWriteFiled(){
		  return statementWriteFiled;
	  }
	  public Map<Class<?>, Map<StatementKind, List<StatementKind>>> getStatementDependences(){
		  return dependences;
	  }
	  public Map<Class<?>, Map<String, List<StatementKind>>> getPrivateDependences(){
		  return privateDependences;
	  }

	  /**
	   * The main entry of building relations between methods
	   * */
	  void buildRelations(List<StatementKind> models) throws IOException {
	    //analyze each class
	    for(Class<?> cls : cluster) {
	    	if(!Filter.canUse(cls)) continue;
		    if(Filter.isPrimitiveOrStringType(cls)) continue;
		    if(cls.equals(Object.class)) continue;

	      ClassReader cr = new ClassReader(cls.getName());
	      ClassNode cn = new ClassNode();
	      cr.accept(cn, ClassReader.SKIP_FRAMES);
	      Map<Class<?>, ReadWriteFields> constructorsReadWrites = analyzeConstructorClass(cls, cn);
	      if(constructorsReadWrites.get(cls)!=null)
	    	  	this.constructorReadWrites.put(cls, constructorsReadWrites.get(cls));
		  if(!classesToTest.contains(cls)) continue;
	      Map<Method, ReadWriteFields> methodReadWrites = analyzeClass(cls, cn);
	      
	      this.fieldReadWrites.put(cls, methodReadWrites);
	    }
//	    System.out.println();
	    this.mergeCallEffects();
	    this.mergeConstructorEffects();
	    System.out.println("Compute method dependence ...");
	    Log.log("Compute method dependence ...");
	    this.computeDependence(models);
	    this.computeDependenceConstructor(models);
	    Log.log("Compute statement write fileds ...");
	    this.buildWriteFiledMethodDependence(models);
	  }
	 private void buildWriteFiledMethodDependence(List<StatementKind> models){
		    //keep a map to speed up lookup
		    Map<String, StatementKind> methodStmtMap = this.buildMethodStatementMapping(models);
	  //classify constructors
			Map<Class<?>, List<StatementKind>> constructors = new LinkedHashMap<Class<?>, List<StatementKind>>();
			for(StatementKind statement : models) {
			  Class<?> owner = this.getDeclaringClass(statement);
			  if(!constructors.containsKey(owner)) {
			    constructors.put(owner, new LinkedList<StatementKind>());
			  }
			  if(statement instanceof ConstructorDeclared) {
			    constructors.get(owner).add(statement);
			  }
			}

		  for(Entry<Class<?>, Map<Method, ReadWriteFields>> entry : this.fieldReadWrites.entrySet()) {
			  Class<?> clazz=entry.getKey();
		      Map<String, List<StatementKind>> statementMap =
			        new LinkedHashMap<String, List<StatementKind>>();

			  Map<Method, ReadWriteFields> methodSummary = entry.getValue();
			  for(Entry<Method, ReadWriteFields> mfields: methodSummary.entrySet()) {
				  ReadWriteFields fields = mfields.getValue();
			       for(Entry<String, Integer> writes : fields.writeFields.entrySet()) {
			    	   String fieldName=writes.getKey().toString();
//			    	   System.err.println("fieldName: " + fieldName);
				        StatementKind statementWrietField =  methodStmtMap.get(mfields.getKey().toGenericString());// this.getStatement(models, methodEntry.getKey());
				        if(statementWrietField == null) {
				          //System.err.println("No key? " + methodEntry.getKey());
				          continue;
				        }
			    	    if(statementMap.get(fieldName) == null){
			    	    	statementMap.put(fieldName, new LinkedList<StatementKind>());
					        // add constructors
//					        List<StatementKind> ctors = constructors.get(clazz);
//					        if(ctors != null && !ctors.isEmpty()) {
//					          statementMap.get(fieldName).addAll(ctors);
//					        }

			    	    }
			    		List<StatementKind> methodList = (List<StatementKind>) statementMap.get(fieldName);
			    		if(!methodList.contains(statementWrietField))
			    				methodList.add(statementWrietField);
 
			       }

			  }
			  if(statementMap.size()==0) continue;
		      this.statementWriteFiled.put(clazz, statementMap);
		    }
		  for(Entry<Class<?>, ReadWriteFields> mfields: this.constructorReadWrites.entrySet()){
			  Class<?> cls=mfields.getKey();
			  Map<String, List<StatementKind>> statementMap =this.statementWriteFiled.get(cls);
			  if(statementMap==null)
				  statementMap= new LinkedHashMap <String, List<StatementKind>>();   
			  ReadWriteFields fields = mfields.getValue();
			  for(Entry<String, Integer> writes : fields.writeFields.entrySet()) {
				  String fieldName=writes.getKey().toString();
		    	    if(statementMap.get(fieldName) == null)
		    	    	statementMap.put(fieldName, new LinkedList<StatementKind>());

				  List<StatementKind> statementWrietField =  constructors.get(cls);
				  if(statementWrietField==null) continue;
				  List<StatementKind> methodList = (List<StatementKind>) statementMap.get(fieldName);
				  if(!methodList.contains(statementWrietField))
	    				methodList.addAll(statementWrietField);


		  }
			  this.statementWriteFiled.put(cls, statementMap);
		  }

		    //reclaim the memory
		    methodStmtMap.clear();


	  }
	  /**
	   * Returns a list of related method
	   * */
	  List<StatementKind> getRelatedMethods(Method method) {
	    Class<?> owner = method.getDeclaringClass();
	    if(!this.dependences.containsKey(owner)) {
	      return new LinkedList<StatementKind>();
	    }
	    if(!this.dependences.get(owner).containsKey(method)) {
	      return new LinkedList<StatementKind>();
	    }
	    return this.dependences.get(owner).get(method);
	  }
	  
	  
	  /**
	   * All private methods below
	   * */
	  @SuppressWarnings("unchecked")
	  private Map<Method, ReadWriteFields> analyzeClass(Class<?> cls, ClassNode cn) {
	    Map<Method, ReadWriteFields> methodReadWrites =
	      new LinkedHashMap<Method,ReadWriteFields>();
	    //analyze each method
	    List<MethodNode> methodNodes = cn.methods;    
	    for(MethodNode methodNode : methodNodes) {
	      Method method = this.getMethod(cls, methodNode);
	      if(method == null) {
	        continue;
	      }
//	      System.out.println("analyze Method:"+ method.toGenericString());
//	      System.out.println("return type:"+ method.getReturnType());
		    Class<?>[] args=method.getParameterTypes();
	//	    	System.out.println("Argument Types:"+ Arrays.asList(args));

	      ReadWriteFields readWrites = this.getReadWriteFields(cls, methodNode);
	      // Add the return and argumnets typs class
	      readWrites.argusTyp(new HashSet<Class<?>>(Arrays.asList(args)));
	      readWrites.returnTyp(method.getReturnType());
	      
	      methodReadWrites.put(method, readWrites);
	    }
	    
	    //merge call sequence here
	//    return this.mergeCallEffects(methodReadWrites);
	    return methodReadWrites;
	  }
	  @SuppressWarnings("unchecked")
	  private Map<Class<?>, ReadWriteFields> analyzeConstructorClass(Class<?> cls, ClassNode cn) {
	    Map<Class<?>, ReadWriteFields> methodReadWrites =
	      new LinkedHashMap<Class<?>,ReadWriteFields>();
	    //analyze each method
	    List<MethodNode> methodNodes = cn.methods;    
	    for(MethodNode methodNode : methodNodes) {
	    	 if(!methodNode.name.equals("<init>")) continue;
	//    	System.out.println("analyze Constructor Class:"+ cls.getName());

	      ReadWriteFields readWrites = this.getReadWriteFields(cls, methodNode);
	      if(!methodReadWrites.containsKey(cls))
	    	  	methodReadWrites.put(cls, readWrites);
	      else{
	    	  ReadWriteFields readWritesAlreadySaved=methodReadWrites.get(cls);
       	      for(Entry<String, Integer> writes : readWrites.writeFields.entrySet()) 
       	    	  readWritesAlreadySaved.write(writes.getKey().toString());
   	  			
       	   methodReadWrites.put(cls, readWritesAlreadySaved);	       	  		

	      }
	    }
	    
	    //merge call sequence here
	//    return this.mergeCallEffects(methodReadWrites);
	    return methodReadWrites;
	  }

	  private ReadWriteFields getReadWriteFields(Class<?> cls, MethodNode methodNode) {
			List<String> uitlFields=new LinkedList<String>();
		    ReadWriteFields readWrites = new ReadWriteFields();
		    InsnList mlist = methodNode.instructions;
	    	 
		    for(int i = 0; i < mlist.size(); i++) {
		      AbstractInsnNode insn = mlist.get(i);
		      int opCode = insn.getOpcode();
		      
		      switch(opCode) {
		      case ARRAYLENGTH:
		    	  if(uitlFields.size()!=0){
		 //       	  System.out.println("read array length:"+ uitlFields.get(0));
		    		  uitlFields.remove(uitlFields.size()-1);
		    	  }
		    	  break;
		      case LASTORE:
		      case FASTORE:
		      case DASTORE:
		      case IASTORE :
		      case CASTORE:
		      case AASTORE:
		      case BASTORE:
		      	  if(uitlFields.size()!=0){
			    	  readWrites.write(uitlFields.get(uitlFields.size()-1));
					  uitlFields.remove(uitlFields.size()-1);
		      	  }
		    	  
		    	  break;
		      case INVOKEINTERFACE:

		    	  MethodInsnNode  javaUtilMethod = (MethodInsnNode)insn;
		    	  
		  // 	  System.out.println(javaUtilMethod.getType() + "  "+ javaUtilMethod.getClass().getName() +" " +javaUtilMethod.desc + " " + javaUtilMethod.owner + " "+ javaUtilMethod.name);
		    	  if(RequestMethodType.contains(javaUtilMethod.name))
		    		  if(uitlFields.size()!=0)
		    			  readWrites.write(uitlFields.get(uitlFields.size()-1));

		          if(uitlFields.size()!=0)
					  uitlFields.remove(uitlFields.size()-1);
		  
		          readWrites.call(this.getMethod(cls.getName(), javaUtilMethod.name, javaUtilMethod.desc));

		    	  break;
		      case GETFIELD:
		          FieldInsnNode fieldGet = (FieldInsnNode)insn;
		           uitlFields.add(fieldGet.name);
		          readWrites.read(fieldGet.name);
		          break;
		        case PUTFIELD:
		          FieldInsnNode fieldPut = (FieldInsnNode)insn;
		          if(uitlFields.size()!=0&&uitlFields.contains(fieldPut.name))
		        	  	uitlFields.remove(uitlFields.size()-1);
		          readWrites.write(fieldPut.name);
		          break;
		        case GETSTATIC:
		          FieldInsnNode fieldStaticGet = (FieldInsnNode)insn;
		         if(fieldStaticGet.owner.startsWith("java")) continue;
		          if(!fieldStaticGet.owner.contains(cls.getName()))
		        	  uitlFields.add(fieldStaticGet.name);

		          readWrites.read(fieldStaticGet.name);
		          break;
		        case PUTSTATIC:
		          FieldInsnNode fieldStaticPut = (FieldInsnNode)insn;
		          if(!fieldStaticPut.desc.contains(cls.getName()))
		        	  uitlFields.add(fieldStaticPut.name);
		          readWrites.write(fieldStaticPut.name);
		          break;
		        case INVOKEVIRTUAL:
		        	
		          MethodInsnNode virtualMethod = (MethodInsnNode)insn;
		          if(virtualMethod.owner.startsWith("instrumentation")) continue;
		          if(virtualMethod.owner.startsWith("java/io/PrintStream")) continue;
		          if(!virtualMethod.owner.contains(cls.getName())||virtualMethod.owner.contains("[")){
		        	  if(RequestMethodType.contains(virtualMethod.name)){
		        		  if(uitlFields.size()!=0){
			        		  readWrites.write(uitlFields.get(uitlFields.size()-1));
			        		  uitlFields.remove(uitlFields.size()-1);
			        		 }       	  
		        	  break;
		        	  }
		         }
		       
		          if(virtualMethod.owner.startsWith("java")) continue;
		         
		          readWrites.call(this.getMethod(virtualMethod.owner.replace('/', '.'), virtualMethod.name, virtualMethod.desc));
		          break;
		        case INVOKESTATIC:
		          MethodInsnNode staticMethod = (MethodInsnNode)insn;
		          if(staticMethod.owner.startsWith("instrumentation")) continue;
			
		          readWrites.call(this.getMethod(staticMethod.owner.replace('/', '.'), staticMethod.name, staticMethod.desc));
		          break;         
		        case INVOKESPECIAL:
		            MethodInsnNode privateMethod = (MethodInsnNode)insn;
		            if(privateMethod.name.equals("<init>"))
		            	readWrites.callClass(this.getClass(privateMethod.owner.replace('/', '.'), privateMethod.name, privateMethod.desc));
		            else
		            	readWrites.call(this.getMethod(privateMethod.owner.replace('/', '.'), privateMethod.name, privateMethod.desc));
		            break;   
		        }
		    }
		    
		    return readWrites;
		  }

	  private Method getMethod(Class<?> cls, MethodNode methodNode) {
		  return this.getMethod(cls.getName(), methodNode.name, methodNode.desc);
	  }
	  
	  private Method getMethod(String clsOwner, String methodName, String methodDesc)   {
		    for(Class<?> clazz : cluster) {
		    	if(!clsOwner.equals(clazz.getName())) continue;
				  Method[] methods = clazz.getDeclaredMethods();
				//	  Method[] methods = cls.getMethods();
				    for(Method method : methods) {
				      if(method.getName().equals(methodName)&& Type.getMethodDescriptor(method).equals(methodDesc)) {
				        return method;
				      }
				    }
			    Class<?> javaClasses= clazz.getSuperclass();
			     methods = javaClasses.getDeclaredMethods();
			     for(Method method : methods) {
			         if(method.getName().equals(methodName)&& Type.getMethodDescriptor(method).equals(methodDesc)) {
			           return method;
			         }
			       }
				    }
	    return null;

	  }
	  private Class<?> getClass(String clsOwner, String methodName, String methodDesc)   {
		    for(Class<?> clazz : cluster) {
		    	if(!clsOwner.equals(clazz.getName())) continue;
		    		return clazz;
		    }
		    return null;
	  }
	  private Map<Class<?>, ReadWriteFields> mergeConstructorEffects() {
			 for(Entry<Class<?>, ReadWriteFields> Classes : this.constructorReadWrites.entrySet()) {
				 Class<?> thisCls=Classes.getKey();
				  ReadWriteFields mergedReadWrites = Classes.getValue().cloneWithoutCalls();
			      Set<Class<?>> alreadyVisited = new HashSet<Class<?>>();
			      List<Class<?>> worklist = new LinkedList<Class<?>>();
			      worklist.addAll(Classes.getValue().callClass());
			      alreadyVisited.addAll(Classes.getValue().callClass());
			      while(!worklist.isEmpty()) {
			        Class<?> first = worklist.remove(0);
			        //skip public calls
//			        if(Modifier.isPublic(first.getModifiers())) {
//			          continue;
//			        }
				      //put the merged read/write to the return map
			//        mergedReadWrites.calls.addAll(alreadyVisited);

			       // Class<?> clzCall=first.getDeclaringClass();
			         ReadWriteFields fields= this.constructorReadWrites.get(first);
//			        if(methodCallReadWrites==null) continue;
			 //       ReadWriteFields fields = methodCallReadWrites.get(first);
			        
			        if(fields == null) {
			          continue;
			        }
			        for(Entry<String, Integer> reads : fields.readFields.entrySet()) {
			          mergedReadWrites.read(reads.getKey(), reads.getValue());
			        }
			        for(Entry<String, Integer> writes : fields.writeFields.entrySet()) {
			          mergedReadWrites.write(writes.getKey(), writes.getValue());
			        }
			        for(Class<?> c : fields.callClass) {
			          if(!alreadyVisited.contains(c)) {
			            alreadyVisited.add(c);
			            worklist.add(c);	       
			            if(!mergedReadWrites.callClass.contains(c))
			            	mergedReadWrites.callClass.add(c);

			          }
			        }
			      }
			      //reclaim memory
			      alreadyVisited.clear();
			      worklist.clear();
			      this.constructorReadWrites.put(thisCls, mergedReadWrites);
			 }
			 
			 return null;
	  }
	    
	  private Map<Method, ReadWriteFields> mergeCallEffects() {
	    //create a new map
	 for(Entry<Class<?>, Map<Method, ReadWriteFields>> Classes : this.fieldReadWrites.entrySet()) {
		Map<Method, ReadWriteFields> methodReadWrites = Classes.getValue();

	    Map<Method, ReadWriteFields> retMap = new LinkedHashMap<Method, ReadWriteFields>();
	    
	    //merge effects transitively
	    for(Entry<Method, ReadWriteFields> entry : methodReadWrites.entrySet()) {
	      Method method = entry.getKey();
//	      System.out.println("merge effects: "+method);	
	      ReadWriteFields mergedReadWrites = entry.getValue().cloneWithoutCalls();
	      
	      //using a standard worklist algorithm
	      Set<Method> alreadyVisited = new HashSet<Method>();
	      List<Method> worklist = new LinkedList<Method>();
	      worklist.addAll(entry.getValue().calls());
	      alreadyVisited.addAll(entry.getValue().calls());
	      while(!worklist.isEmpty()) {
	        Method first = worklist.remove(0);
	        //skip public calls
//	        if(Modifier.isPublic(first.getModifiers())) {
//	          continue;
//	        }
		      //put the merged read/write to the return map
	//        mergedReadWrites.calls.addAll(alreadyVisited);

	        Class<?> clzCall=first.getDeclaringClass();
	        Map<Method, ReadWriteFields> methodCallReadWrites= this.fieldReadWrites.get(clzCall);
	        if(methodCallReadWrites==null) continue;
	        ReadWriteFields fields = methodCallReadWrites.get(first);
	        
	        if(fields == null) {
	          continue;
	        }
	        for(Entry<String, Integer> reads : fields.readFields.entrySet()) {
	          mergedReadWrites.read(reads.getKey(), reads.getValue());
	        }
	        for(Entry<String, Integer> writes : fields.writeFields.entrySet()) {
	          mergedReadWrites.write(writes.getKey(), writes.getValue());
	        }
	        for(Method m : fields.calls) {
	          if(!alreadyVisited.contains(m)) {
	            alreadyVisited.add(m);
	            worklist.add(m);	       
	            if(!mergedReadWrites.calls.contains(m))
	            	mergedReadWrites.calls.add(m);

	          }
	        }
	      }
	      
	      retMap.put(method, mergedReadWrites);
	      //reclaim memory
	      alreadyVisited.clear();
	      worklist.clear();
	      
	     
	      
	    }
	    this.fieldReadWrites.put(Classes.getKey(), retMap);
		    }
	    //reclaim the memory
	//    methodReadWrites.clear();
	    //return retMap;
	    return null;
	  }
	  private void computeDependenceConstructor(List<StatementKind> models) {	
		  
				Map<Class<?>, List<StatementKind>> constructors = new LinkedHashMap<Class<?>, List<StatementKind>>();
				for(StatementKind statement : models) {
				  Class<?> owner = this.getDeclaringClass(statement);
				  if(!constructors.containsKey(owner)) {
				    constructors.put(owner, new LinkedList<StatementKind>());
				  }
				  if(statement instanceof ConstructorDeclared) {
				    constructors.get(owner).add(statement);
				  }
				}
				 for(Entry<Class<?>, ReadWriteFields> entry : this.constructorReadWrites.entrySet()) {
					 Class<?> cls=entry.getKey();
					  List<StatementKind> ctors = constructors.get(cls);
					  if(ctors==null) continue;
				      List<StatementKind> dependentStatements = new LinkedList<StatementKind>(ctors);

 					 ReadWriteFields CallsReadWrites = entry.getValue();
					    Set<Class<?> > callThisClass = CallsReadWrites.callClass();
					    for(Class<?> callCls:callThisClass){
					    	if(constructors.containsKey(callCls)){
				        		  ctors = constructors.get(callCls);
					      	        if(ctors != null && !ctors.isEmpty()&& !dependentStatements.containsAll(ctors))
					      	        	dependentStatements.addAll(ctors);
					      	        
					    	}
					    }
					    
					     // StatementKind key =  constructors.get(cls);
					      if(dependentStatements.size()==0) continue;
					      this.constructorDependences.put(cls, dependentStatements);
				 }
	  }
	  /**
	   * Compute the dependence between each methods
	   * */
	  private void computeDependence(List<StatementKind> models) {	
			Map<Class<?>, List<StatementKind>> constructors = new LinkedHashMap<Class<?>, List<StatementKind>>();
			for(StatementKind statement : models) {
			  Class<?> owner = this.getDeclaringClass(statement);
			  if(!constructors.containsKey(owner)) {
			    constructors.put(owner, new LinkedList<StatementKind>());
			  }
			  if(statement instanceof ConstructorDeclared) {
			    constructors.get(owner).add(statement);
			  }
			}

		    for(Entry<Class<?>, Map<Method, ReadWriteFields>> entry : this.fieldReadWrites.entrySet()) {
				    Class<?> clazz = entry.getKey();
				    Map<Method, ReadWriteFields> methodAndReadWrites = entry.getValue();		      
		      		//the method dependence map
				    Map<StatementKind, List<StatementKind>>  methodMap = new LinkedHashMap<StatementKind, List<StatementKind>>();
		        	methodMap = this.computeRelatedMethod(methodAndReadWrites,constructors,models);		      
		        	//put the class and method dependence to the map
		        	this.dependences.put(clazz, methodMap);
		    }
		  }
	  
	 private Map<StatementKind, List<StatementKind>>  computeRelatedMethod(Map<Method, ReadWriteFields> methodAndReadWrites,Map<Class<?>, List<StatementKind>> constructors,List<StatementKind> models) {
		 Map<StatementKind, List<StatementKind>> relatedStatementsMap = new LinkedHashMap<StatementKind, List<StatementKind>>();

		 Map<String, StatementKind> methodStmtMap = this.buildMethodStatementMapping(models);

	    //then compute the dependence between each method
	    for(Method method : methodAndReadWrites.keySet()) {
	      //other methods
	  //   Set<Method> otherMethods = methodAndReadWrites.keySet();
	      //otherMethods.remove(method);
	    	System.out.println("this: "+method);
    	 
	    	 if(!Filter.canUse(method)) continue;
	      //the list add to the related method map
		        Set<String> readThisMethod = methodAndReadWrites.get(method).readFields.keySet();
		        Set<String> writeThisMethod = methodAndReadWrites.get(method).writeFields.keySet();
		        Set<Method> callThisMethod = methodAndReadWrites.get(method).calls();

	      List<Method> dependentMethods = new LinkedList<Method>();
	      List<StatementKind> dependentStatements = new LinkedList<StatementKind>();

	      for(Entry<Class<?>, Map<Method, ReadWriteFields>> entry : this.fieldReadWrites.entrySet()) {
	    	  Class<?> className=entry.getKey();
	    	  Map<Method, ReadWriteFields> methodAndReadWritesClass = entry.getValue();
		      Set<Method> otherMethods = methodAndReadWritesClass.keySet();
	      for(Method otherMethod : otherMethods) {

	        if(otherMethod == method) {
	          continue;
	        }
//	        System.out.println("\t other:"+otherMethod);	
	        if(otherMethod.getName().equals("toString")) continue;


	      //  Set<String> readOtherMethod = methodAndReadWrites.get(otherMethod).readFields.keySet();
	        Set<String> writeOtherMethod = methodAndReadWritesClass.get(otherMethod).writeFields.keySet();
	        Set<Method> callOtherMethod = methodAndReadWritesClass.get(otherMethod).calls();
	        //this argumnets type
	        Set<Class<?>> argsThisMethod = methodAndReadWrites.get(method).argusTyp();
	        //other return type
	        Set<Class<?>> retrnOtherMethod = methodAndReadWritesClass.get(otherMethod).returnTyp();
	        //calculate private call methods
		       // this.privateDependences.put(clazz, statementMap);
			      Map<String, List<StatementKind>> statementMap =
				        new LinkedHashMap<String, List<StatementKind>>();
		        for(Method m:callThisMethod){
		        	if(!Filter.canUse(otherMethod)){
				        String pMethodName=privateMethdoName(m);
				        if(this.privateDependences.get(className)==null)
				        	this.privateDependences.put(className, statementMap);
				        statementMap=this.privateDependences.get(className);
				        if(statementMap==null||statementMap.size()==0)
				        		statementMap.put(pMethodName, new LinkedList<StatementKind>());
				        List<StatementKind> list=statementMap.get(pMethodName);
				        if(list==null)
				        	list=new LinkedList<StatementKind>();
				        if(!list.contains(methodStmtMap.get(method.toGenericString())))
				        	list.add(methodStmtMap.get(method.toGenericString()));
				        statementMap.put(pMethodName, list);
				        this.privateDependences.put(className, statementMap);
		        	}
		        }
		    	 if(!Filter.canUse(otherMethod)) continue;

	        //it should compute reversely
		        Set<String> readWriteFields = new HashSet<String>();
		        Set<String> readReadFields = new HashSet<String>();
		        //for(String readField : readOtherMethod) {
		        for(String writeField : writeOtherMethod) { 
		          if(writeThisMethod.contains(writeField)) {
		            readWriteFields.add(writeField);
		          }
		          if(readThisMethod.contains(writeField)) {
		            readReadFields.add(writeField);
		            if(!dependentMethods.contains(otherMethod)){
	            		dependentMethods.add(otherMethod);
		            	StatementKind key =  methodStmtMap.get(otherMethod.toGenericString());
		            	dependentStatements.add(key);
		            }
		          }
		        }
		       // See if public other method calls this method.
		        if(Filter.canUse(otherMethod)){
//		        	if(callThisMethod.contains(otherMethod))
//		        		dependentMethods.add(otherMethod);
		        	for( Method m: callOtherMethod){
		        		if(m.equals(method)){
		        			if(!dependentMethods.contains(otherMethod)){
		        				StatementKind key =  methodStmtMap.get(otherMethod.toGenericString());
			            		dependentStatements.add(key);
		        					dependentMethods.add(otherMethod);
		        			}
		        			// add all the methods that write in the other method that call this method
		        		}
		        	}
		        }
		        Class<?> retrnTpe= retrnOtherMethod.iterator().next();
	//	        System.out.println("\t other:"+retrnTpe);	  
		        if(!Filter.isPrimitiveOrStringType(retrnTpe)&&!retrnTpe.equals(void.class)&&!retrnTpe.toString().contains("java"))
			        if(argsThisMethod.contains(retrnTpe)||argsThisMethod.contains(Object.class))
			            if(!dependentMethods.contains(otherMethod)){
		            		dependentMethods.add(otherMethod);
	        				StatementKind key =  methodStmtMap.get(otherMethod.toGenericString());
	        				if(key!=null)
	        					dependentStatements.add(key);

			            }
		        // return type for constructors
		        List<StatementKind> ctors = constructors.get(className);
		     	List<Class<?>> argsTpe=Arrays.asList(method.getParameterTypes());
	             for(Class<?> clz:argsTpe){
	    			 if(clz.equals(Object.class)
	    						||clz.equals(java.util.HashMap.class)
	    						|| clz.equals(Comparable.class) ) continue;
	        	     if(Filter.isPrimitiveOrStringType(clz) )continue;
			         if(Filter.isAbstract(clz) || !Filter.isVisible(clz.getModifiers())) {
			        	 for(Class<?> clz2:constructors.keySet())
			        		 if(CanBeUsedAs.canBeUsedAs(clz2, clz)){
			        			  ctors = constructors.get(clz2);
					      	        if(ctors != null && !ctors.isEmpty()&& !dependentStatements.containsAll(ctors)) {
					      	        	dependentStatements.addAll(ctors);
					      	        }
			        		 }
			         }else{
			        	 if(constructors.containsKey(clz)){
			        		  ctors = constructors.get(clz);
				      	        if(ctors != null && !ctors.isEmpty()&& !dependentStatements.containsAll(ctors)) {
				      	        	dependentStatements.addAll(ctors);
				      	        }
//	        			  System.err.println();
			        	 }
			         	}

			         }
			     

		        //Calculate constructions
		        ReadWriteFields fields= this.constructorReadWrites.get(className);
		        if(fields!=null)
				  for(Entry<String, Integer> writes : fields.writeFields.entrySet()) {
					  String writeField=writes.getKey().toString();
			    	    if(readThisMethod.contains(writeField)){
							  List<StatementKind> statementWrietField =  constructors.get(className);
						  if(statementWrietField!=null)
							  if(!dependentStatements.containsAll(statementWrietField))
								  dependentStatements.addAll(statementWrietField);
			    	    }
				  }

	      }
	      }
	      
	      StatementKind key =  methodStmtMap.get(method.toGenericString());
	      if(dependentStatements.size()==0) continue;
	      relatedStatementsMap.put(key, dependentStatements);
	    }
	    return relatedStatementsMap;
	  }
	  
	  /**
	   * A helper method to get the declaring class of the given statement
	   * */
	  private Class<?> getDeclaringClass(StatementKind statement) {
	    if(statement instanceof MethodDeclared) {
	    		MethodDeclared rmethod = (MethodDeclared)statement;
	      return rmethod.getMethod().getDeclaringClass();
	    } else if(statement instanceof ConstructorDeclared) {
	    	ConstructorDeclared CreateObject = (ConstructorDeclared)statement;
	      return CreateObject.getConstructor().getDeclaringClass();
	    } else {
	      throw new RuntimeException("Unexpected statement type here: " + statement);
	    }
	  }
	  
	  /**
	   * Convert the method dependence to be statement dependence
	   * */
	  public  String privateMethdoName(Method m) {
		  
		  String name=m.toString();
//		  if(name.contains("private final java.net.URI org.jdom2.Element.resolve"))
//			  System.err.println("MethodRelations_.privateMethdoName: "+name);
		  name=name.substring(name.indexOf(m.getReturnType().getName().toString()));
		  name= name.replace(m.getDeclaringClass().getName().toString()+".", "".toString());
		 int throwIndx= name.indexOf(") throws ");
		 if(throwIndx>0){
			 //name=name.substring(throwIndx+1, name.length());
			 name= name.replace(name.substring(throwIndx+1, name.length()), "".toString());
		 }

	//	  System.err.println("MethodRelations_.privateMethdoName: "+name);

		     StringBuilder sb = new StringBuilder();
		     sb.append(name);
		     
		     return sb.toString();
		   }

	  private Map<String, StatementKind> buildMethodStatementMapping(List<StatementKind> models) {
	    Map<String, StatementKind> methodStmtMap = new LinkedHashMap<String, StatementKind>();
	    for(StatementKind statement : models) {
	      if(statement instanceof MethodDeclared) {
	        String signature = ((MethodDeclared)statement).getMethod().toGenericString();
	        methodStmtMap.put(signature, statement);
	      }
	    }
	    
	    return methodStmtMap;
	  }
	  /**
	   * Debugging help, to show the content
	   * */
	  String showDependence() {
	    StringBuilder sb = new StringBuilder();
	    
	    for(Entry<Class<?>, Map<StatementKind, List<StatementKind>>> entry : this.dependences.entrySet()) {
//		     if(!classesToTest.contains(entry.getKey()))continue;

		      Map<StatementKind, List<StatementKind>> methodMap = entry.getValue();
	      sb.append("Class: ");
	      sb.append(entry.getKey());
	      sb.append("\nconstructors");
	      List<StatementKind> callClass =this.constructorDependences.get(entry.getKey());
	      if(callClass!=null){
	        for(StatementKind s : callClass) {
		          sb.append("\n\t"+ s.convertString());
		        }
	      }

	      for(Entry<StatementKind, List<StatementKind>> methods: methodMap.entrySet()) {
	    	  if(methods.getKey()==null)continue;
	        sb.append("\n\nmethod: ");
	        sb.append(methods.getKey().convertString());
	        List<StatementKind> methodsWrite = methods.getValue();
	        sb.append("\t" + methodsWrite.size());
	        for(StatementKind s : methodsWrite) {
	          sb.append("\n\t"+ s.convertString());
	        }
	        sb.append(lineSep );
	      }
	      sb.append(lineSep+lineSep );
	    }
	    
	    
	     return sb.toString();
	  }
	  String showStatmentsWriteFileds() {
		    StringBuilder sb = new StringBuilder();
		    for(Entry<Class<?>, Map<String, List<StatementKind>>> entry : this.statementWriteFiled.entrySet()) {
			     if(!classesToTest.contains(entry.getKey()))continue;

			      sb.append("Class: ");
			      sb.append(entry.getKey());
			      Map<String, List<StatementKind>> methodMap = entry.getValue();
			      for(Entry<String, List<StatementKind>> mfields: methodMap.entrySet()) {
				        sb.append("\nFiled: ");
				        sb.append(mfields.getKey());
//				        sb.append(lineSep);
				        List<StatementKind> methods = mfields.getValue();
				        sb.append("\t" + methods.size());
				        for(StatementKind s : methods) {
				          sb.append("\n\t" + s.convertString());
//				          sb.append(lineSep);
				        }
				      }
//				      sb.append(lineSep + lineSep);

				    
		    }
		    return sb.toString();
	  }	  
	  /**
	   * Debugging help, to show the content
	   * */
	  String showFieldReadWrites() {
	    StringBuilder sb = new StringBuilder();
	    
	   for(Entry<Class<?>, Map<Method, ReadWriteFields>> entry : this.fieldReadWrites.entrySet()) {
		     if(!classesToTest.contains(entry.getKey()))continue;
		 sb.append("Class: ");

	     sb.append(entry.getKey());
	     sb.append(lineSep);
	     Map<Method, ReadWriteFields> methodSummary = entry.getValue();
	     for(Entry<Method, ReadWriteFields> mfields: methodSummary.entrySet()) {
	       sb.append("  method: ");
	       sb.append(mfields.getKey());
	       sb.append(lineSep);
	       ReadWriteFields fields = mfields.getValue();
	       sb.append("    All reads:" + lineSep);
	       for(Entry<String, Integer> reads : fields.readFields.entrySet()) {
	         sb.append("       " + reads.getKey()+ ", " + (reads.getValue()));
	         sb.append(lineSep);
	       }
	       sb.append("    All writes:" + lineSep);
	       for(Entry<String, Integer> writes : fields.writeFields.entrySet()) {
	         sb.append("       " + writes.getKey()+ ", " + (writes.getValue()));
	         sb.append(lineSep);
	       }
	       sb.append("    All Calls:" + lineSep);
	       for(Method mCall : fields.calls) {
	         sb.append("       " + mCall.toGenericString());
	         sb.append(lineSep);
	       }

	     }
	     sb.append(lineSep + lineSep);
	   }
	    
	    return sb.toString();
	  }

}